/**
 * Copyright 2018 人人开源 http://www.renren.io
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package io.renren.modules.oss.cloud;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import io.renren.common.exception.RRException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * 阿里云存储
 * @author chenshun
 * @email sunlightcs@gmail.com
 * @date 2017-03-26 16:22
 */
@Slf4j
public class AliyunCloudStorageService extends CloudStorageService {
    private OSS client;

    public AliyunCloudStorageService(CloudStorageConfig config){
        this.config = config;
        // System.out.println(this.config.getSaveCallBackUrl());
        //初始化
        init();
    }

    private void init(){
        try {
            client = new OSSClientBuilder().build(config.getAliyunEndPoint(), config.getAliyunAccessKeyId(), config.getAliyunAccessKeySecret());
        }
        catch (Exception e)
        {
            System.out.println("aliyun oss init failed!请检查配置参数！");
        }
    }

    @Override
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    @Override
    public String uploadConverted(InputStream inputStream, String path, String taskid, String versionOfLite,int size,String fileType) {
        try {
            // PutObjectResult putObjectResult = client.putObject(config.getAliyunBucketName(), path, inputStream);


            // The callback url，for example: http://oss-demo.aliyuncs.com:23450或http://0.0.0.0:9090
            // The service of that url must support the post method.
            // final String callbackUrl = saveCallBackUrl;
            //final String callbackUrl = "http://wis3d.tpddns.cn:9099/fileserver/userfile/files/saveFileToDatabase";
            OSS ossClient = client;

            try {

                PutObjectRequest putObjectRequest = new PutObjectRequest(config.getAliyunBucketName(), path, inputStream);

                // PutObjectRequest putObjectRequest = new PutObjectRequest(config.getAliyunBucketName(), path, new File("C:\\Users\\dgt\\Desktop\\test.prt"));


                Callback callback = new Callback();
                System.out.println("this.config.getSaveCallBackUrl()========================"+this.config.getSaveCallBackUrl());

                String md5 = DigestUtils.md5Hex(inputStream);
                callback.setCallbackUrl(this.config.getSaveCallBackUrl());
                //callback.setCallbackHost("oss-cn-hangzhou.aliyuncs.com");
                callback.setCallbackBody("{\\\"bucket\\\":${bucket},\\\"object\\\":${object},"
                        + "\\\"mimeType\\\":${mimeType},"
                        + "\\\"path\\\":${x:path},\\\"taskid\\\":${x:taskid},\\\"versionOfLite\\\":${x:versionOfLite},\\\"size\\\":${x:size},\\\"fileType\\\":${x:fileType}}");// ,\"fileMd5\":${x:fileMd5}
                callback.setCalbackBodyType(Callback.CalbackBodyType.JSON);
                callback.addCallbackVar("x:path", path);
                callback.addCallbackVar("x:taskid", taskid);
                callback.addCallbackVar("x:versionOfLite", versionOfLite);
                callback.addCallbackVar("x:size", String.valueOf(size));
                callback.addCallbackVar("x:fileType", String.valueOf(fileType)); // 文件类型
                // callback.addCallbackVar("x:fileMd5", md5);
                putObjectRequest.setCallback(callback);

                PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
                byte[] buffer = new byte[1024];
                putObjectResult.getResponse().getContent().read(buffer);
                putObjectResult.getResponse().getContent().close();

                System.out.println("aliyun getRequestId ========================"+putObjectResult.getResponse().getRequestId());

            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message: " + oe.getErrorMessage());
                System.out.println("Error Code:       " + oe.getErrorCode());
                System.out.println("Request ID:      " + oe.getRequestId());
                System.out.println("Host ID:           " + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message: " + ce.getMessage());
            } finally {
                ossClient.shutdown();
            }
        } catch (Exception e){
            throw new RRException("上传文件失败，请检查配置信息", e);
        }

        return config.getAliyunDomain() + "/" + path;
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        try {
            PutObjectResult putObjectResult = client.putObject(config.getAliyunBucketName(), path, inputStream);
        } catch (Exception e){
            throw new RRException("上传文件失败，请检查配置信息", e);
        }

       return config.getAliyunDomain() + "/" + path;
    }

    @Override
    public String upload(MultipartFile multipartFile, String path, String uploadId)
    {
        try {
            // 分片上传
            /* 步骤1：初始化一个分片上传事件。
             */
            OSS ossClient = client;
            String bucketName = config.getAliyunBucketName();
            String  objectName = path;
//            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
//            InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
//            // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
//            uploadId = result.getUploadId();

            /* 步骤2：上传分片。
             */
            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags =  new ArrayList<PartETag>();
            // 计算文件有多少个分片。
            final long partSize = 1 * 1024 * 1024L;   // 1MB
            long fileLength = multipartFile.getSize();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }
            // 遍历分片上传。
            File f = null;
            try {
                int idx =  multipartFile.getOriginalFilename().lastIndexOf(".");
                String suffix = multipartFile.getOriginalFilename().substring(idx);
                f = File.createTempFile("tmp", suffix);
                multipartFile.transferTo(f);
                f.deleteOnExit();
            } catch (Exception e) {
                e.printStackTrace();
            }
            final File sampleFile = f;//new File("./"+ multipartFile.getOriginalFilename());
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                InputStream instream = multipartFile.getInputStream();//new FileInputStream(sampleFile); //
                // 跳过已经上传的分片。
                instream.skip(startPos);
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(objectName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
                uploadPartRequest.setPartNumber( i + 1);
                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                // 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
                partETags.add(uploadPartResult.getPartETag());
            }

            /* 步骤3：完成分片上传。
             */
            // 排序。partETags必须按分片号升序排列。
            Collections.sort(partETags, new Comparator<PartETag>() {
                public int compare(PartETag p1, PartETag p2) {
                    return p1.getPartNumber() - p2.getPartNumber();
                }
            });
            // 在执行该操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
            ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            System.out.println("aliyun分片上传uploadId：" + uploadId);
            // 关闭OSSClient。
            // ossClient.shutdown();
        } catch (Exception e){
            throw new RRException("分片上传文件失败，请检查配置信息", e);
        }

        return uploadId;
    }
    @Override
    public void cancelUpload(String uploadId, String key)
    {
        // 创建OSSClient实例。
        OSS ossClient = client;
        try {
            // 取消分片上传，其中uploadId来自于InitiateMultipartUpload。
            AbortMultipartUploadRequest abortMultipartUploadRequest =
                    new AbortMultipartUploadRequest(config.getAliyunBucketName(), key, uploadId);
            ossClient.abortMultipartUpload(abortMultipartUploadRequest);
        }catch (OSSException e)
        {
            System.out.println("com.aliyun.oss.OSSException: The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        // 关闭OSSClient。
        // ossClient.shutdown();
    }
    @Override
    public String uploadSuffix(byte[] data, String suffix) {
        return upload(data, getPath(config.getAliyunPrefix(), suffix));
    }

    @Override
    public String uploadSuffix(InputStream inputStream, String suffix) {
        return upload(inputStream, getPath(config.getAliyunPrefix(), suffix));
    }

    /**
     * @Author wangchengwei
     * @Description  OSS下载文件
     * @Date 2019/6/18 9:44
     * @Param [objectName]
     * @return inputStream 需要在调用方关闭流
     **/
    @Override
    public CloudStream download(String objectName)
    {
        String bucketName = config.getAliyunBucketName();
        OSSObject ossObject = null;
        try {
            if(client.doesObjectExist(bucketName,objectName)) {
                // 调用ossClient.getObject返回一个OSSObject实例，该实例包含文件内容及文件元信息。
                ossObject = client.getObject(bucketName, objectName);
            }
        }catch (OSSException e)
        {
            // OSS在查找不到某对象时，会抛出ErrorCode为“NoSuchKey”的OSSException，而不是返回null
            if (e.getErrorCode().contains("NoSuchKey"))
            {
                System.out.println("找不到OSS文件：" + objectName);
                return null;
            }
            else
            {
                e.printStackTrace();
            }
        }
        if(null != ossObject) {
            // 调用ossObject.getObjectContent获取文件输入流，可读取此输入流获取其内容。
            InputStream content = ossObject.getObjectContent();
            long length = ossObject.getObjectMetadata().getContentLength();
            if (content != null) {
                return new CloudStream(content, length);
            } else {
                throw new RRException("获取文件流失败!");
            }
        }
        else
        {
            throw new RRException("找不到OSS文件：" + objectName);
        }
    }

    /**
     * @Author wangchengwei
     * @Description  文件下载到本地
     * @Date 2019/6/19 10:16
     * @Param [objectName, desPath]
     * @return 
     **/
    public void download(String objectName, String desPath)
    {
        String bucketName = config.getAliyunBucketName();
        if(client.doesObjectExist(bucketName,objectName)) {
            // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖，不存在则新建。
            client.getObject(new GetObjectRequest(bucketName, objectName), new File(desPath));
        }
    }

    /**
     * @Author wangchengwei
     * @Description  列举指定存储空间下的文件。默认列举100个文件。
     * @Date 2019/6/18 9:45
     * @Param
     * @return 
     **/
    public void list()
    {
        String bucketName = config.getAliyunBucketName();
        // ossClient.listObjects返回ObjectListing实例，包含此次listObject请求的返回结果。
        ObjectListing objectListing = client.listObjects(bucketName);
        // objectListing.getObjectSummaries获取所有文件的描述信息。
        for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
            System.out.println(" - " + objectSummary.getKey() + "  " +
                    "(size = " + objectSummary.getSize() + ")");
        }
    }
    
    /**
     * @Author wangchengwei
     * @Description  列举指定存储空间中指定前缀下的文件
     * @Date 2019/6/18 9:48
     * @Param [prefix]
     * @return 
     **/
    public void list(String prefix)
    {
        String bucketName = config.getAliyunBucketName();
        /*
         * List objects in your bucket by prefix
         */
        System.out.println("Listing objects");
        ObjectListing objectListing = client.listObjects(bucketName, prefix);
        for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
            System.out.println(" - " + objectSummary.getKey() + "  " +
                    "(size = " + objectSummary.getSize() + ")");
        }
    }

    /**
     * @Author wangchengwei
     * @Description  删除文件
     * @Date 2019/6/18 9:47
     * @Param [objectName]
     * @return 
     */
    public void delete(String objectName)
    {
        String bucketName = config.getAliyunBucketName();
        if(null == objectName || null == bucketName)
        {
            return;
        }
        try {
            // 判断文件存在才删除文件
            if(client.doesObjectExist(bucketName,objectName)) {
                client.deleteObject(bucketName, objectName);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
